package gu.sql2java.generator;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.StringTokenizer;

/**
 * 该枚举类表示不同的数据库引擎。<p>
 * 每个枚举值代表一种特定的数据库引擎，如 MySQL、Phoenix、SQLite 等。<br>
 * 它提供了判断指定数据库连接、模式、表中的指定列是否为自动更新列的方法，<br>
 * 不同的数据库引擎可能有不同的实现逻辑。
 * 
 * @author guyadong
 * @since 3.34.0
 */
public enum DatabaseVendor {
	MYSQL("MySQL"){
		@Override
		protected Boolean doDetectsAutoUpdateInColumn(Connection conn, String schema, String tableName, String columnName)
				throws SQLException {
			try (PreparedStatement preparedStatement = prepareColumnMetadataQuery(conn, schema, tableName, columnName);
					ResultSet rs = preparedStatement.executeQuery()) {
				if (rs.next()) {
					String extra = rs.getString("EXTRA");
					return null == extra ? false : extra.toUpperCase().contains("ON UPDATE");
				}else {
					throw new SQLException("NOT FOUND COLUMN : " + columnName + " in TABLE : " + tableName + " in SCHEMA : "
							+ schema + " in DATABASE : " + conn.getCatalog() + "");
				}
			}
		}
		/**
		 * 准备一个用于查询指定数据库模式、表和列的元数据的预编译语句。<p>
		 * 该方法构建一个 SQL 查询语句，用于从 information_schema.columns 表中获取指定列的元数据信息。<br>
		 * 然后使用传入的数据库连接对象创建并配置预编译语句，将模式名、表名和列名作为参数绑定到预编译语句中。<p>
		 * 
		 * @param conn 数据库连接对象，用于创建预编译语句，不能为 null。
		 * @param schema 数据库模式名，用于指定查询的数据库模式，不能为 null 或空字符串。
		 * @param tableName 表名，用于指定查询的表，不能为 null 或空字符串。
		 * @return 一个配置好的预编译语句对象，可用于执行查询操作。
		 * @throws SQLException 如果在创建预编译语句过程中发生 SQL 异常，将抛出该异常。
		 */
		private PreparedStatement prepareColumnMetadataQuery(Connection conn, String schema, String tableName,
				String columnName) throws SQLException {
			String sql = "SELECT * FROM information_schema.columns "
					+ "WHERE TABLE_SCHEMA= ? AND TABLE_NAME = ? AND COLUMN_NAME = ?";
			PreparedStatement preparedStatement = conn.prepareStatement(sql);
			preparedStatement.setString(1, schema);
			preparedStatement.setString(2, tableName);
			preparedStatement.setString(3, columnName);
			return preparedStatement;
		}
		
	},
	PHOENIX("Phoenix"),
	SQLITE("SQLite");
	/**
	 * 数据库引擎的产品名称。<p>
	 * 该属性存储了每个数据库引擎对应的产品名称，例如 "MySQL"、"Phoenix"、"SQLite" 等。<br>
	 * 该名称在枚举类初始化时被赋值，且一旦赋值后不可更改。
	 */
	public final String productName;
	private DatabaseVendor(String productName) {
		this.productName = productName;
	}

	/**
	 * 判断指定数据库连接、模式、表中的指定列是否为自动更新列。<p>
	 * 不同的数据库引擎可能会有不同的实现逻辑。<br>
	 * 默认返回{@code null}(未知)。不同的数据库引擎需要根据自身的规则重写该方法以实现不同的判断逻辑。<p>
	 * 返回值说明：<br>
	 * <ul>
	 * <li>true: 自动更新字段</li>
	 * <li>false: 非自动更新字段</li>
	 * <li>null: 未知</li>
	 * </ul>
	
	 * @param conn 数据库连接对象，用于执行数据库查询操作，不能为 null。<br>
	 * @param schema 数据库模式名，用于指定查询的数据库模式，不能为 null 或空字符串。<br>
	 * @param tableName 表名，用于指定查询的表，不能为 null 或空字符串。<br>
	 * @param columnName 列名，用于指定要判断的列，不能为 null 或空字符串。<br>
	 * @return 如果确定知道指定列是否自动更新列。返回 <code>true</code> 或 <code>false</code>；否则返回 <code>null</code>。
	 * @throws SQLException 如果在执行数据库操作过程中发生 SQL 异常，将抛出该异常。
	 */
	protected Boolean doDetectsAutoUpdateInColumn(Connection conn, String schema, String tableName, String columnName) throws SQLException {
		return null;
	}


	/**
	 * 判断指定数据库连接、模式、表中的指定列是否为自动更新列。<p>
	 * 该方法会先对传入的参数进行有效性检查，确保数据库连接对象不为 null，<br>
	 * 数据库模式名、表名和列名不为 null 或空字符串。<br>
	 * 然后调用具体数据库引擎实现的 <code>doDetectsAutoUpdateInColumn</code> 方法进行判断。<p>
	 * 返回值说明：<br>
	 * <ul>
	 * <li>true: 自动更新字段</li>
	 * <li>false: 非自动更新字段</li>
	 * <li>null: 未知</li>
	 * </ul>
	 * 
	 * @param conn 数据库连接对象，用于执行数据库查询操作，不能为 null。
	 * @param schema 数据库模式名，用于指定查询的数据库模式，不能为 null 或空字符串。
	 * @param tableName 表名，用于指定查询的表，不能为 null 或空字符串。
	 * @param columnName 列名，用于指定要判断的列，不能为 null 或空字符串。
	 * @return 如果确定知道指定列是否自动更新列。返回 <code>true</code> 或 <code>false</code>；否则返回 <code>null</code>。
	 * @throws SQLException 如果在执行数据库操作过程中发生 SQL 异常，将抛出该异常。
	 */
	final boolean detectsAutoUpdateInColumn(Connection conn, String schema, String tableName, String columnName) throws SQLException {
		checkArgument(conn != null, "conn is null");
		checkArgument(!isNullOrEmpty(schema), "schema is null or empty");
		checkArgument(!isNullOrEmpty(tableName), "tableName is null or empty");
		checkArgument(!isNullOrEmpty(columnName), "columnName is null or empty");
		return doDetectsAutoUpdateInColumn(conn, schema, tableName, columnName);
	}

	/**
	 * 根据数据库产品名称获取对应的 {@link DatabaseVendor} 枚举实例。<p>
	 * 该方法会对传入的数据库产品名称进行处理，提取第一个标记，<br>
	 * 然后遍历所有的 {@link DatabaseVendor} 枚举值，<br>
	 * 比较提取的标记与枚举值的 {@link #productName}（忽略大小写），<br>
	 * 如果匹配则返回对应的枚举实例。<br>
	 * 如果没有找到匹配的枚举实例，则抛出 {@link SQLException} 异常。<p>
	 * 
	 * @param databaseProductName 数据库产品名称，不能为 null。
	 * @return 与指定数据库产品名称对应的 {@link DatabaseVendor} 枚举实例。
	 * @throws SQLException 如果没有找到与指定数据库产品名称对应的枚举实例，将抛出该异常。
	 */
	static DatabaseVendor fromProductName(String databaseProductName) throws SQLException{
		String name = new StringTokenizer(checkNotNull(databaseProductName,"databaseProductName is null")).nextToken();
		for (DatabaseVendor vendor : DatabaseVendor.values()) {
			if(vendor.productName.equalsIgnoreCase(name)){
				return vendor;
			}
		}
		throw new SQLException("NOT SUPPORT DATABASE VENDOR : " + databaseProductName);
	}
	/**
	 * 根据给定的数据库连接对象获取对应的 {@link DatabaseVendor} 枚举实例。<p>
	 * 该方法会先从传入的数据库连接对象中获取数据库产品名称，<br>
	 * 然后调用 {@link #fromProductName(String)} 方法，根据产品名称查找对应的枚举实例。<p>
	 * 
	 * @param conn 数据库连接对象，用于获取数据库产品名称，不能为 null。
	 * @return 与指定数据库连接对应的 {@link DatabaseVendor} 枚举实例。
	 * @throws SQLException 如果在获取数据库产品名称过程中发生 SQL 异常，或者没有找到对应的枚举实例，将抛出该异常。
	 */
	static DatabaseVendor getDatabaseVendor(Connection conn) throws SQLException{
		String databaseProductName = checkNotNull(conn,"conn is null").getMetaData().getDatabaseProductName();
		return fromProductName(databaseProductName);
	}
}
